/**
 * position.js - Javascript chess library
 *
 * this is a require.js module
 *
 * http://code.google.com/p/fics-meets-html5/
 *
 * Copyright 2012, 2013 Markus Kupke
 */

define( function() {

    var Position_internal;

// Public Interface
    var Position = function(fen){
        var pos_internal = new Position_internal(fen);
        var position = {};
        position.toJSON = function(){
            return pos_internal.toJSON();
        }
//TODO change format of return object
        position.legalmoves = function(){
            return pos_internal.legalmoves();
        }
//TODO check return value
        position.apply_move = function(move){
            return pos_internal.move(move);
        }
        return position;
    }

//  Private part starts here


//	Position_internal is the constructor for positions
//  which is used internally
//	if called with EPD or FEN it will setup board accordingly
//	otherwise it will have initial chess setup
    Position_internal = function Position_internal(fen) {
        "use strict";
        if (fen && typeof fen === 'string') {
            var rows = fen.split('/');
            var field = 1, i, j, n;
            var regexp_castling = /^(K?Q?k?q?|-)$/;
            var rank;
            var filelettersposition;

            if (rows.length !== 8) throw new Position_internal.FENError('FEN not valid');
            var add_attributes = rows[7].split(/[ \t]+/);
            if (add_attributes.length < 4 || add_attributes.length > 6) throw new Position_internal.FENError('FEN: too many attributes for move right, castling, en passant');
            rows[7] = add_attributes[0];
            if (add_attributes[1] !== 'w' && add_attributes[1] !== 'b') throw new Position_internal.FENError('FEN not okay; unclear who as right to move');
            this.white2move = add_attributes[1] === 'w';
            if (!regexp_castling.test(add_attributes[2])) throw new Position_internal.FENError('FEN: problem with castling string');
            this.castling = add_attributes[2];
            this.clearboard();
            if (add_attributes[3] !== '-') {
                if (add_attributes[3].length !== 2) throw new Position_internal.FENError("FEN: en passant field not correct");
                filelettersposition = this.constructor.fileletters.indexOf(add_attributes[3][0]);
                if (filelettersposition === -1) throw new Position_internal.FENError("FEN: No file in en passant field");
                filelettersposition++;
                rank = Number(add_attributes[3][1]);
                if (!rank && rank > 8) throw new Position_internal.FENError("FEN: en passant field not correct");
                filelettersposition += (rank - 1) * 8;
                this[this.constructor.from_64_to_internal_board_representation(filelettersposition)] = 'e';
            }
            for (i = 7; i >= 0; i--) {
                for (j = 0; j < rows[i].length; j++) {
                    if (n = Number(rows[i][j])) {
                        if (n < 1 || n > 8) throw new Position_internal.FENError('FEN not valid, numbers must be between 1 and 8');
                        field += Number(rows[i][j]);
                    } else {
                        if (Position_internal.allowedletters.indexOf(rows[i][j]) === -1) throw new Position_internal.FENError('Malformed FEN string');
                        this[this.constructor.from_64_to_internal_board_representation(field++)] = rows[i][j];
                    }
                }
            }
            if (!this.isvalid()) throw new Position_internal.FENError('Position contains inconsistencies');
        }
    };

//	constants, offset and internal board representation from
//	"Schach am PC" by Dieter Steinwender and Frederic A. Friedel
	Position_internal.offset_knight = [ 19 , 21 , 12 , -8 , -19 , -21 , -12 , 8 ];
	Position_internal.offset_other = [{off:[ -9 , -11 , 9, 11 ],
		pieces:['QBK', 'qbk']
	},
	{off:[ -1 , 10 , 1 , -10 ],
		pieces:['QRK', 'qrk']
	}];
	Position_internal.fileletters="abcdefgh";
	Position_internal.allowedletters = 'kKqQpPrRnNbB';
	Position_internal.FENError = function FENError (){};
	Position_internal.FENError.prototype = Object.create(Error.prototype);
	Position_internal.FENError.prototype.constructor = Position_internal.FENError;
	Position_internal.Move = function Move (){};
	Position_internal.Move.prototype = Object.create(Object.prototype);
	Position_internal.Move.prototype.constructor = Position_internal.Move;
	Position_internal.Move.prototype.toString = function(){
		var s = this.figure + Position_internal.internalboard2humanboard(this.from) +
		(this.capture?"x":"-") + Position_internal.internalboard2humanboard(this.to);
		if(this.hasOwnProperty('changetofigure')) s += this.changetofigure;
		return s;
	};
	Position_internal.from_64_to_internal_board_representation = function( i ) {
		"use strict";
		if( i<1 || i>64 ) { 
			throw {
				name	: "from_64_to_internal_board_representation: boudary violation",
				message	: "from_64_to_internal_board_representation: A chess board does have 64 fields :-)"
			};
		}
		return i + Math.floor((i-1)/8) * 2 + 20;
	};

//	the board is part of the prototype: every position will inherit it
//	a move on the board will be represented by prototypal inheritance
	Position_internal.prototype[21]	= 'R';
	Position_internal.prototype[22]	= 'N';
	Position_internal.prototype[23]	= 'B';
	Position_internal.prototype[24]	= 'Q';
	Position_internal.prototype[25]	= 'K';
	Position_internal.prototype[26]	= 'B';
	Position_internal.prototype[27]	= 'N';
	Position_internal.prototype[28]	= 'R';
	Position_internal.prototype[31]	= 'P';
	Position_internal.prototype[32]	= 'P';
	Position_internal.prototype[33]	= 'P';
	Position_internal.prototype[34]	= 'P';
	Position_internal.prototype[35]	= 'P';
	Position_internal.prototype[36]	= 'P';
	Position_internal.prototype[37]	= 'P';
	Position_internal.prototype[38]	= 'P';
	Position_internal.prototype[41]	= '0';
	Position_internal.prototype[42]	= '0';
	Position_internal.prototype[43]	= '0';
	Position_internal.prototype[44]	= '0';
	Position_internal.prototype[45]	= '0';
	Position_internal.prototype[46]	= '0';
	Position_internal.prototype[47]	= '0';
	Position_internal.prototype[48]	= '0';
	Position_internal.prototype[51]	= '0';
	Position_internal.prototype[52]	= '0';
	Position_internal.prototype[53]	= '0';
	Position_internal.prototype[54]	= '0';
	Position_internal.prototype[55]	= '0';
	Position_internal.prototype[56]	= '0';
	Position_internal.prototype[57]	= '0';
	Position_internal.prototype[58]	= '0';
	Position_internal.prototype[61]	= '0';
	Position_internal.prototype[62]	= '0';
	Position_internal.prototype[63]	= '0';
	Position_internal.prototype[64]	= '0';
	Position_internal.prototype[65]	= '0';
	Position_internal.prototype[66]	= '0';
	Position_internal.prototype[67]	= '0';
	Position_internal.prototype[68]	= '0';
	Position_internal.prototype[71]	= '0';
	Position_internal.prototype[72]	= '0';
	Position_internal.prototype[73]	= '0';
	Position_internal.prototype[74]	= '0';
	Position_internal.prototype[75]	= '0';
	Position_internal.prototype[76]	= '0';
	Position_internal.prototype[77]	= '0';
	Position_internal.prototype[78]	= '0';
	Position_internal.prototype[81]	= 'p';
	Position_internal.prototype[82]	= 'p';
	Position_internal.prototype[83]	= 'p';
	Position_internal.prototype[84]	= 'p';
	Position_internal.prototype[85]	= 'p';
	Position_internal.prototype[86]	= 'p';
	Position_internal.prototype[87]	= 'p';
	Position_internal.prototype[88]	= 'p';
	Position_internal.prototype[91]	= 'r';
	Position_internal.prototype[92]	= 'n';
	Position_internal.prototype[93]	= 'b';
	Position_internal.prototype[94]	= 'q';
	Position_internal.prototype[95]	= 'k';
	Position_internal.prototype[96]	= 'b';
	Position_internal.prototype[97]	= 'n';
	Position_internal.prototype[98]	= 'r';
	Position_internal.prototype.length = 99;
	Position_internal.prototype.white2move = true;
	Position_internal.prototype.castling = 'KQkq';

	Position_internal.prototype.legalmoves = function(){
		"use strict";
		var moves = [], pseudo_moves = [], m, p_after_move;
		var dir = this.white2move?-1:1;
		var p;
		if(!(this.hasOwnProperty("legals"))){
			pseudo_moves = this.pseudomoves();
			for (m in pseudo_moves) {
				if (!(pseudo_moves.hasOwnProperty(m))) continue;
				p = pseudo_moves[m];
				if (p.castling) {moves.push(p);continue;} //castling moves are already checked for validity
				p_after_move = Object.create(this); // now we use prototypical inheritance to produce a position after a move
				p_after_move.white2move = !this.white2move;
				p_after_move[p.from] = '0';
				p_after_move[p.to] = p.figure;
				if(this[p.to] === 'e'){//we have an en passant capture
					p_after_move[(p.to)+10*dir] = '0';
				}
				if(p.changetofigure) p_after_move[p.to] = p.changetofigure;
				if(p_after_move.isvalid())
					moves.push(p);
			};
			this.legals = moves;
		}
		return this.legals;
	};


	Position_internal.prototype.pseudomoves = function(){
		//generate all pseudomoves regardless check
		var moves = [];
		var pieces_pos = []; //holds the positions of the figure we are interested in
		var move;
		var i, j, o, p, p2, figures, piece_pos, figset;
		var friends = this.white2move ? 'QBNRPK' : 'qbnrpk';
		var second_row, eight_row;
		var offset_knight = this.constructor.offset_knight;
		var offset, dir;
		var offset_other = this.constructor.offset_other;
		var Move = this.constructor.Move;
		var changefigures = this.white2move ? 'QRBN':'qrbn';
		var king = this.white2move ? 'K':'k';
		var castling_kingside_letter, castling_queenside_letter;
		var castling_kingside_fields, castling_queenside_fields;

		if(!(this.hasOwnProperty('pseudos'))){

			//start with the knights
			var knight = this.white2move ? 'N' : 'n';
			Array.prototype.forEach.call(this, function(fig, i){
				if (fig === knight) pieces_pos.push(i);
			});
			for (i in pieces_pos) {
				if(!pieces_pos.hasOwnProperty(i)) continue;
				piece_pos = pieces_pos[i];
				for (o in offset_knight) {
					if(!offset_knight.hasOwnProperty(o)) continue;
					p = piece_pos + offset_knight[o];
					if(!this[p]) continue;
					if(!(friends.indexOf(this[p]) === -1)) continue; //not allowed to capture own pieces
					move = new Move;
					move.figure = knight;
					move.from = piece_pos;
					move.to = p;
					if(!(this[p] === '0' || this[p] === 'e')) {
						move.capture = true;
					}
					moves.push(move);
				};
			};
			//next the other pieces others than pawn
			//diagonal first then the files
			for(figset = 0; figset <=1 ; figset ++){
				offset = offset_other[figset].off;
				figures = this.white2move ? offset_other[figset].pieces[0]:offset_other[figset].pieces[1];
				pieces_pos.length = 0;
				Array.prototype.forEach.call(this, function(fig, i){
					if (!(figures.indexOf(fig) === -1)) pieces_pos.push(i);
				});
				for (i in pieces_pos) {
					if(!pieces_pos.hasOwnProperty(i)) continue;
					piece_pos = pieces_pos[i];
					for(o in offset){
						if(!offset.hasOwnProperty(o)) continue;
						for (j=1;j<8;j++){
							if(this[piece_pos]===king && j ===2) break;
							p = piece_pos + offset[o] * j;
							if(!this[p])break;
							if(!(friends.indexOf(this[p]) === -1)) break; //not allowed to capture own pieces
							move = new Move;
							move.figure = this[piece_pos];
							move.from = piece_pos;
							move.to = p;
							if(!(this[p] === '0' || this[p] === 'e')) {
								move.capture = true;
								moves.push(move);
								break;
							}
							moves.push(move);
						}
					};
				};	
			}
			//now the pawns
			figures = this.white2move ? 'P':'p';
			dir = this.white2move ? 1 : -1;
			second_row = this.white2move ? 30 : 80;
			eight_row = this.white2move ? 90 : 20;
			pieces_pos.length = 0;
			Array.prototype.forEach.call(this, function(fig, i){
				if (fig === figures) pieces_pos.push(i);
			});
			for (i in pieces_pos){
				if(!(pieces_pos.hasOwnProperty(i))) continue;
				piece_pos = pieces_pos[i];
				for (offset = 9; offset <= 11; offset=offset + 2) {   //capture moves
					p = piece_pos + dir * offset; 
					if(!this[p]) continue;
					if (this[p]==='0'||(!(friends.indexOf(this[p])===-1))) continue;
					move = new Move;
					move.figure = this[piece_pos];
					move.from = piece_pos;
					move.to = p;
					move.capture = true;
					if(this[p]==='e') {
						move.pawnToRemove = p - dir * 10;
					};
					moves.push(move);
				};
				p = piece_pos + dir * 10;
				if (!(this[p]==='0')) continue;
				if (0 < (p - eight_row) && (p - eight_row) < 9) { //if the pawn steps to the eighth row it becomes a different figure
					for (j = 0; j < changefigures.length; j++){
						move = new Move;
						move.figure = figures;
						move.from = piece_pos;
						move.to = p;
						move.changetofigure = changefigures[j];
						moves.push(move);
					}
				} else {
					move = new Move;
					move.figure = figures;
					move.from = piece_pos;
					move.to = p;
					moves.push(move);
					if (0 < (piece_pos - second_row) && (piece_pos - second_row) < 9) { // a pawn on the second row may step two fields at once
						p2 = p + dir * 10;
						if (this[p2]==='0')
						{
							move = new Move;
							move.figure = figures;
							move.from = piece_pos;
							move.to = p2;
							move.enpassant = p;
							moves.push(move);
						};
					};
				};
			}
			//castling; we don't regard castling as possible pseudo move if king or field between
			//king and rook is threatened

			castling_kingside_letter = this.white2move?'K':'k';
			castling_queenside_letter = this.white2move?'Q':'q';
			castling_kingside_fields = this.white2move?[26,27]:[96,97];
			castling_queenside_fields = this.white2move?[22,23,24]:[92,93,94];
			kings_position = this.white2move?25:95;
			var king_in_check;
			if(!(this.castling.indexOf(castling_kingside_letter)===-1)) {
				if(this[castling_kingside_fields[0]]==='0'&&this[castling_kingside_fields[1]]==='0'){
					if(king_in_check = this.threats(kings_position,!this.white2move)){
						if(this.threats(castling_kingside_fields[0],!this.white2move)&&this.threats(castling_kingside_fields[1],!this.white2move)){
							move = new Move;
							move.figure = king;
							move.from = kings_position;
							move.to = castling_kingside_fields[1];
							move.castling = true;
							moves.push(move);
						};
					};
				};
			}
			if(!(this.castling.indexOf(castling_queenside_letter)===-1)) {
				if(this[castling_queenside_fields[0]]==='0'&&this[castling_queenside_fields[1]]==='0'&&this[castling_queenside_fields[2]]==='0'){
					if (king_in_check === undefined) king_in_check = this.threats(kings_position,!this.white2move);
					if (king_in_check) {
						if(this.threats(castling_queenside_fields[1],!this.white2move)&&this.threats(castling_queenside_fields[2],!this.white2move)){
							move = new Move;
							move.figure = king;
							move.from = kings_position;
							move.to = castling_queenside_fields[1];
							move.castling = true;
							moves.push(move);
						}
					}
				}
			};
			this.pseudos = moves;
		};
		return this.pseudos;
	};

	Position_internal.prototype.move = function(m){
		var newPos = Object.create(this);
		if (!(m instanceof Position_internal.Move)) throw new Error("This is not a move");
		newPos.white2move = !newPos.white2move;
		// first delete old en passant information
		var idx = Array.prototype.indexOf.call(newPos, 'e');
		if (!(idx === -1)) newPos[idx] = '0';
		newPos[m['from']] = '0';
		newPos[m['to']] = m['figure'];
		if (m['changetofigure']) newPos[m['to']] = m['changetofigure'];
		if (m['enpassant']) newPos[m['enpassant']] = 'e';
		if (m["pawnToRemove"]) newPos[m["pawnToRemove"]] = '0'; 
		if (m.castling) {
			if ((m['from'] - m['to'])>0) { //queen side castling
				newPos[m['from'] - 1] = newPos[m['from'] - 4];
				newPos[m['from'] - 4] = '0';
			} else { //king side castling
				newPos[m['from'] + 1] = newPos[m['from'] + 3];
				newPos[m['from'] + 3] = '0';
			};
		};
		return newPos;
	};

	Position_internal.prototype.threats = function(field, white, all){
		// examines the threats on a given field; white gives the side that influences
		// the field; if true all white threats on a field are examined
		// in order to speed up the routine to check whether the king is in check we
		// use all: if true the routine gives back all threats in an array 
		// with values of internal representation. If false the routine
		// stops after the first threat and gives back false (or true if no threat).
		"use strict";
		var threats = [];
		var i, p, loopi, checkpieces, dir, pawn;
		var friends = white ? 'qbnrpk' : 'QBNRPK';
		// performance
		var offset_knight = this.constructor.offset_knight;
		var offset_other = this.constructor.offset_other;

		if (!this[field])
			throw new Error("function threats must have a valid field to check");
		if (white !== true && white !== false)
			throw new Error(
			"function threats needs to know the color that imposes threats");
		// check whether a knight threatens the field
		var knight2check = white ? 'N' : 'n';
		for (i in offset_knight) {
			if (!offset_knight.hasOwnProperty(i))
				continue;
			p = field + offset_knight[i];
			if (!this[p])
				continue;
			if (this[p] === knight2check)
				if (all)
					threats.push(p);
				else
					return false;
		}
		;

		// check whether queen, rook or bishop threatens the field
		for (i in offset_other) {
			if (!(offset_other.hasOwnProperty(i)))
				continue;
			checkpieces = white ? offset_other[i]["pieces"][0]
			: offset_other[i]["pieces"][1];
			for (dir in offset_other[i]["off"]) {
				if (!offset_other[i]["off"].hasOwnProperty(dir))
					continue;
				for (loopi = 1; loopi < 9; loopi++) {
					p = field + loopi * offset_other[i]["off"][dir];
					if (!this[p])
						break; // we are off the board
					if (this[p] === '0' || this[p] === 'e')
						continue;
					if (!(friends.indexOf(this[p]) === -1))
						break; // in this direction is a friend
					if (checkpieces.indexOf(this[p]) === -1) {
						break; // bless the lord; it is a rook or a pawn
					} else if (checkpieces.indexOf(this[p]) === 2 && loopi > 1) {
						break; // the king is too far away
					} else {
						if (all) {
							threats.push(p);
							break
						} else
							return false;
					}
				}
			}
		}
		// now take care of the pawns
		dir = white ? -1 : 1;
		pawn = white ? 'P' : 'p';
		p = this[field + (dir * 11)];
		if (p) {
			if (p === pawn) {
				if (all) {
					threats.push(p)
				} else
					return false;
			}
		}
		p = this[field + (dir * 9)];
		if (p) {
			if (p === pawn)
				if (all) {
					threats.push(p)
				} else
					return false;
		}
		return true;
	};

	Position_internal.prototype.isvalid = function(){
		//checks whether the position is valid;
		"use strict";
		// the side that moved must not be in check
		var king2check = this.white2move?'k':'K';
		var king_position = Array.prototype.indexOf.call(this, king2check);
		return this.threats(king_position, this.white2move, false);
	};
	Position_internal.prototype.clearboard = function(){
		"use strict";
		var i;
		for(i=1;i<=64;i=i+1){
			this[this.constructor.from_64_to_internal_board_representation(i)] = '0';
		};
	};


	Position_internal.internalboard2humanboard = function (pos){
		"use strict";
		var letters="abcdefgh";
		return letters[(pos%10)-1] + (Math.floor(pos/10) - 1).toString();
	};

//	toString of a position prints out FEN of the position
	Position_internal.prototype.toString = function(){
		var i, j, string, empty_counter, en_passant, sym;
		en_passant = '-';
		string = "";
		empty_counter = 0;
		for (i=7;i>=0;i--){
			for (j=1;j<9;j++){
				sym = this[this.constructor.from_64_to_internal_board_representation(i*8+j)]; 
				if(sym ==='0'){
					empty_counter++;
					if(j<8)continue;
				}
				if(sym ==="e"){ //en_passant
					empty_counter++;
					en_passant = this.constructor.fileletters[j-1]+(i+1).toString();
					if(j<8)continue;
				}
				if(empty_counter) string+=empty_counter;
				empty_counter=0;
				if(sym!=='0' && sym!=='e') string+=sym;
			}
			if(i>0)string+="/";
		}
		string+=this.white2move?' w':' b';
		string+=this.castling?' '+this.castling:' -';
		string+=' ' + en_passant;
		return string;
	};

	Position_internal.prototype.toJSON = function (){
		o = {};
		for (var i = 21; i <= 98; i++) {
			if(!(this[i])) continue;
			var field = Position_internal.internalboard2humanboard(i);
			o[field] = this[i];
		};
		return o;
	};


	return Position;
} )  //end of define(function{